/******************************************************************************************************
Copyright (C) 2019 Mestrelab Research S.L. All rights reserved.

This file is part of the MNova scripting toolkit.

Authorized users of MNova Software may use this file freely, but this file is provided AS IS
with NO WARRANTY OF ANY KIND, INCLUDING THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE.
*****************************************************************************************************/

/*globals dbPeakSearchMSInNIST, gc, mainWindow, qsTr, settings, Application, Dir, File, MassItem, MessageBox, MnUi, MoleculeSearcherViewer, ProgressDialog, SysInfo, TextStream, Timer, Process*/

/*jslint plusplus: true, continue: true, indent: 4*/

function DbPeakSearchMSInNIST_ResultsViewer() {
	"use strict";

	//Instance attributes to find the position indexes in the results content
	/*jslint regexp: true */
	this.resultRegex = {};
	this.resultRegex.hitLine = /(Hit\s*\d*\s*:?\s*)([^;]*)/i;
	this.resultRegex.formulaField = /(Hit\s*\d*\s*:?\s*[^;]*\s*;)([^;]*)/i;
	this.resultRegex.matchFactorField = /(MF\s*:\s*:?\s*)([^;]*)/i;
	this.resultRegex.reverseMatchFactorField = /(RMF\s*:\s*:?\s*)([^;]*)/i;
	this.resultRegex.probabilityField = /(Prob\s*:\s*:?\s*)([^;]*)/i;
	this.resultRegex.chemicalAbstractsServiceRegistryNumberField = /(CAS\s*:\s*:?\s*)([^;]*)/i;
	this.resultRegex.molecularWeightField = /(Mw\s*:\s*:?\s*)([^;]*)/i;
	this.resultRegex.libraryField = /(Lib\s*:\s*:?\s*)([^;]*)/i;
	this.resultRegex.idNISTField = /(Id\s*:\s*:?\s*)([^;]*)/i;
	this.resultRegex.retentionIndexField = /(RI\s*:\s*:?\s*)([^\.]*)/i;
	/*jslint regexp: false	*/

	//Field numbers for table widget
	this.fieldNumbers = {};
	this.fieldNumbers.name = 0;
	this.fieldNumbers.formula = 1;
	this.fieldNumbers.matchFactor = 2;
	this.fieldNumbers.reverseMatchFactor = 3;
	this.fieldNumbers.probability = 4;
	this.fieldNumbers.chemicalAbstractsServiceRegistryNumber = 5;
	this.fieldNumbers.molecularWeight = 6;
	this.fieldNumbers.library = 7;
	this.fieldNumbers.idNIST = 8;
	this.fieldNumbers.retentionIndex = 9;

	//Instance attribute for tokens
	this.tokenFieldDelimiter = ";";
	this.notAvilableToken = "NA";

	//Options and miscelaneus
	this.maxLengthForResultHeader = 90;
	this.flagTableSelectionChanged = false;
	this.flagExitButton = false;
	this.flagSearchButton = false;
	this.flagMoleculeSearchButton = false;
	this.searchTypeInMoleculeSearcher = 0; //SearchType: 0 = textSearch; 1 = structure search; 2 = substructure search
	this.listOfEnginesToBeAlwaysActivated = []; //For example ["eMolecules"];
	this.showExitButton = false;
	this.defaultFieldNumberForMoleculeSearchIntegration = this.fieldNumbers.name;

	DbPeakSearchMSInNIST_ResultsViewer.prototype.closeApplication = function () {

		this.flagTableSelectionChanged = this.secureSignalManagement(this.dockWidget.widgets.tableWidget.itemSelectionChanged, this.showResultInMoleculeSearcher, "disconnect", this.flagTableSelectionChanged, this);
		this.flagExitButton = this.secureSignalManagement(this.dockWidget.widgets.toolButtonExit.clicked, this.closeApplication, "disconnect", this.flagExitButton, this);
		this.flagSearchButton = this.secureSignalManagement(this.dockWidget.widgets.toolButtonSearch.clicked, this.searchInNIST, "disconnect", this.flagSearchButton, this);
		this.flagMoleculeSearchButton = this.secureSignalManagement(this.dockWidget.widgets.toolButtonShowRowsInMoleculeSearch.clicked, this.showRowsInMoleculeSearch, "disconnect", this.flagMoleculeSearchButton, this);
		this.dockWidget.close();
		this.dockWidget = undefined;
		if (dbPeakSearchMSInNIST.prototype.backgroundSearch !== undefined) {
			if (dbPeakSearchMSInNIST.prototype.backgroundSearch.process !== undefined) {
				try {
					dbPeakSearchMSInNIST.prototype.backgroundSearch.process.deleteLater();
				} catch (e) {
					//Empty line to handel a possible exception if the deleteLater event has already called
					//This can be performed in the NISTMSSearchFinished method, the slot pointed by the finished signal
				}
			}
		}
		gc();
	};

	DbPeakSearchMSInNIST_ResultsViewer.prototype.searchInNIST = function () {

		dbPeakSearchMSInNIST();
	};

	DbPeakSearchMSInNIST_ResultsViewer.prototype.showRowsInMoleculeSearch = function () {

		settings.setValue(dbPeakSearchMSInNIST.showRowsInMoleculeSearch, this.dockWidget.widgets.toolButtonShowRowsInMoleculeSearch.checked);
	};

	DbPeakSearchMSInNIST_ResultsViewer.prototype.secureSignalManagement = function (aSignal, aSlot, aAction, aFlag, aContext) {

		try {
			if (aAction === "connect" && !aFlag) {
				aSignal.connect(aContext, aSlot);
				aFlag = true;
			}
		} catch (e) {
			try {
				if (aAction === "connect" && !aFlag) {
					aSignal.connect(aContext, aSlot);
					aFlag = true;
				}
			} catch (er) {
				aFlag = false;
			}
		}

		try {
			if (aAction === "disconnect" && aFlag) {
				aSignal.disconnect(aContext, aSlot);
				aFlag = false;
			}
		} catch (err) {
			try {
				if (aAction === "disconnect" && aFlag) {
					aSignal.disconnect(aContext, aSlot);
					aFlag = false;
				}
			} catch (erro) {
				aFlag = false;
			}
		}

		return aFlag;
	};

	DbPeakSearchMSInNIST_ResultsViewer.prototype.clearTableWidget = function () {

		var i, totalRows;

		this.dockWidget.widgets.tableWidget.clearContents();
		totalRows = this.dockWidget.widgets.tableWidget.items.length;
		for (i = 0; i < totalRows; i++) {
			this.dockWidget.widgets.tableWidget.removeRow(0);
		}
	};

	DbPeakSearchMSInNIST_ResultsViewer.prototype.showResultInMoleculeSearcher = function () {

		var i, j, items, currentRow, fieldNumber, fieldValue, engine, engines;

		if (this.dockWidget.widgets.toolButtonShowRowsInMoleculeSearch.checked) {
			if (Application.moleculeSearcher !== undefined) {
				if (Application.moleculeSearcher.molecule !== undefined) {
					if (Application.moleculeSearcher.molecule.flagSearch) {
						if (this.lastRowSelected !== undefined) {
							if (!isNaN(this.lastRowSelected)) {
								if (this.lastRowSelected !== this.dockWidget.widgets.tableWidget.currentRow) {
									this.flagTableSelectionChanged = this.secureSignalManagement(this.dockWidget.widgets.tableWidget.itemSelectionChanged, this.showResultInMoleculeSearcher, "disconnect", this.flagTableSelectionChanged, this);
									this.dockWidget.widgets.tableWidget.selectRow(this.lastRowSelected);
									this.flagTableSelectionChanged = this.secureSignalManagement(this.dockWidget.widgets.tableWidget.itemSelectionChanged, this.showResultInMoleculeSearcher, "connect", this.flagTableSelectionChanged, this);
								}
							}
						}
						MessageBox.warning("The search of Molecule Search is still in progress");
						return;
					}
				}
			}

			//Get compound name for the current row
			currentRow = this.dockWidget.widgets.tableWidget.currentRow;
			items = this.dockWidget.widgets.tableWidget.items;

			//Get the value to be searched by using the right fieldNumber
			fieldNumber = this.defaultFieldNumberForMoleculeSearchIntegration;
			fieldValue = items[currentRow][fieldNumber].text;
			this.lastRowSelected = currentRow;

			//Call to Molecuel Search
			if (fieldValue !== undefined && fieldValue !== "") {
				try {
					if (Application.moleculeSearcher) {
						Application.moleculeSearcher.dockWidget.show();

					} else {
						Application.moleculeSearcher = new MoleculeSearcherViewer();
						if (!Application.moleculeSearcher.dockWidget.floating) {
							Application.moleculeSearcher.dockWidget.floating = true;
							Application.moleculeSearcher.guiAdaptionToFeatures();
						}
					}

					//The eMolecules engine is the right one for this type of search.
					//So we ensure that this engine is active					
					engines = Application.moleculeSearcher.availableEngines;
					for (i = 0; i < engines.length; i++) {
						engine = engines[i];
						for (j = 0; j < this.listOfEnginesToBeAlwaysActivated.length; j++) {
							if (engine.id === this.listOfEnginesToBeAlwaysActivated[j]) {
								engine.active = true;
								Application.moleculeSearcher.guiAdaptionToFeatures();
								break;
							}
						}
					}

					//Peform the search
					Application.moleculeSearcher.molecule.leTextInput.text = fieldValue;
					Application.moleculeSearcher.moleculeSearchManagement(this.searchTypeInMoleculeSearcher);
					Application.mainWindow.installEventHandler("selectionChanged", "Application.moleculeSearcher.onSelectionChanged");
					Application.mainWindow.installEventHandler("MouseButtonPress", "Application.moleculeSearcher.onMouseButtonPress");
					mainWindow.processEvents();

				} catch (Err) {
					MessageBox.critical(qsTr("Error found calling Molecule Search"));
					Application.moleculeSearcher = undefined;
				}
			}
		}
	};

	DbPeakSearchMSInNIST_ResultsViewer.prototype.showResultsViewer = function (aResultMatrix, aTotalRows, aResultHeader) {

		var i, data, row, items, fieldNumbers, rowNumber, auxResultsHeader;

		if (aResultMatrix !== undefined && aResultMatrix.length > 0 && aTotalRows !== undefined && aTotalRows > 0) {

			if (this.dockWidget === undefined || this.dockWidget === null) {
				this.dockWidget = {};
				this.dockWidget = Application.loadUiFile('ricares:dbPeakSearchMSInNIST_ResultsViewer.ui');
				this.dockWidget.floating = true;
			} else {
				this.flagTableSelectionChanged = this.secureSignalManagement(this.dockWidget.widgets.tableWidget.itemSelectionChanged, this.showResultInMoleculeSearcher, "disconnect", this.flagTableSelectionChanged, this);
				this.clearTableWidget();
			}

			//Restore settings
			data = settings.value(dbPeakSearchMSInNIST.showRowsInMoleculeSearch, "");
			if (data !== undefined && data !== "") {
				data = (data === "true");
				this.dockWidget.widgets.toolButtonShowRowsInMoleculeSearch.checked = data;
			}

			//We need the exit button if it is needed
			if (!this.showExitButton) {
				this.dockWidget.widgets.toolButtonExit.visible = false;
			}

			//Show dialog and create signals
			this.dockWidget.show();
			this.flagTableSelectionChanged  = this.secureSignalManagement(this.dockWidget.widgets.tableWidget.itemSelectionChanged, this.showResultInMoleculeSearcher, "connect", this.flagTableSelectionChanged, this);
			this.flagExitButton = this.secureSignalManagement(this.dockWidget.widgets.toolButtonExit.clicked, this.closeApplication, "connect", this.flagExitButton, this);
			this.flagSearchButton = this.secureSignalManagement(this.dockWidget.widgets.toolButtonSearch.clicked, this.searchInNIST, "connect", this.flagSearchButton, this);
			this.flagMoleculeSearchButton = this.secureSignalManagement(this.dockWidget.widgets.toolButtonShowRowsInMoleculeSearch.clicked, this.showRowsInMoleculeSearch, "connect", this.flagMoleculeSearchButton, this);

			//Results header management for label
			if (aResultHeader !== undefined && aResultHeader !== "") {
				this.dockWidget.widgets.lbResultHeader.toolTip = qsTr("Molecules found for: {0}".format(aResultHeader));
				if (aResultHeader.length > this.maxLengthForResultHeader) {
					auxResultsHeader = "<b>" + aResultHeader.substring(0, this.maxLengthForResultHeader) + " ...</b>";
				}
				this.dockWidget.widgets.lbResultHeader.text = qsTr("Molecules found for: {0}".format(auxResultsHeader));
				this.dockWidget.widgets.lbResultHeader.visible = true;
			} else {
				this.dockWidget.widgets.lbResultHeader.visible = false;
			}

			//Inflate table widget
			for (i = 0; i < aTotalRows; i++) {
				this.dockWidget.widgets.tableWidget.insertRow(0);
			}

			//Fill table with data
			items = this.dockWidget.widgets.tableWidget.items;
			for (i = 0; i < aTotalRows; i++) {
				row = aResultMatrix[i];
				for (fieldNumbers in this.fieldNumbers) {
					if (this.fieldNumbers.hasOwnProperty(fieldNumbers)) {
						try {
							rowNumber = this.fieldNumbers[fieldNumbers];
							if (rowNumber < row.length) {
								items[i][rowNumber].text = row[rowNumber];
								items[i][rowNumber].flags = 33;
								items[i][rowNumber].textAlignment = 132;
							}
						} catch (e) {
							continue;
						}
					}
				}
			}
			this.dockWidget.widgets.tableWidget.items = items;

			//Adapt column size to contents
			for (fieldNumbers in this.fieldNumbers) {
				if (this.fieldNumbers.hasOwnProperty(fieldNumbers)) {
					rowNumber = this.fieldNumbers[fieldNumbers];
					if (rowNumber !== this.fieldNumbers.name) {
						this.dockWidget.widgets.tableWidget.resizeColumnToContents(rowNumber);
					}
				}
			}

			//Select the first hit by default
			//If the Molecule Searcher will be started by default we have to use a new process to auto-select the first row
			//Otherwise, it will be docked (floating to false) by default without knowing the reason why. Something strange happens with signals
			//However, this approach fixes the problem
			if (this.dockWidget.widgets.toolButtonShowRowsInMoleculeSearch.checked) {
				this.auxProcess = new Timer();
				this.auxProcess.singleShot = true;
				this.auxProcess.timeout.connect(this, function () { this.dockWidget.widgets.tableWidget.selectRow(0); });
				this.auxProcess.start(500);
			} else {
				this.dockWidget.widgets.tableWidget.selectRow(0);
			}
			this.dockWidget.raise();

		} else {
			MessageBox.warning(qsTr("No hits found"));
		}
	};

	DbPeakSearchMSInNIST_ResultsViewer.prototype.showResults = function (aResultsContent) {

		var i, content, linesContent, resultLine, regexResult, results, auxFieldValue,
			resultRow, currentRegex, regex, fieldValue, totalRows, resultHeader;

		if (aResultsContent !== undefined && aResultsContent !== "") {
			content = aResultsContent.replace(/\r/g, "");
			linesContent = content.split("\n");

			//Initialize vars
			results = [];
			totalRows = 0;
			resultHeader = "";

			//Parse result in order to find the hit lines
			for (i = 0; i < linesContent.length; i++) {
				resultRow = [];
				resultLine = linesContent[i];

				//Perform a regex for each field
				for (currentRegex in this.resultRegex) {
					if (this.resultRegex.hasOwnProperty(currentRegex)) {
						regex = this.resultRegex[currentRegex];
						regexResult = regex.exec(resultLine);

						if (regexResult !== null) {
							if (regexResult.length > 2) {
								fieldValue = regexResult[2];
								if (currentRegex === "hitLine" || currentRegex === "formulaField") {
									auxFieldValue = fieldValue.toString().replace(/</g, "").replace(/>/g, "");
									fieldValue = auxFieldValue.toString().trim();
								}
								resultRow.push(fieldValue.toString().trim());
							}
						}
						if (resultRow.length === 0) {
							resultRow.push(this.notAvilableToken);
						}
					}
				}

				//Add current row to the matrix
				if (resultRow.length === Object.keys(this.fieldNumbers).length) {
					results.push(resultRow);
					totalRows++;
				} else if (i === 0) {
					resultHeader = resultLine;
				}
			}

			//Show table widget with results
			this.showResultsViewer(results, totalRows, resultHeader);
		}
	};
}

function dbPeakSearchMSInNIST(aOption) {
	"use strict";

	var i, massItem, massSpectrum, flagSpectrum, plot, plotsCount, parameters, peaks, dbNISTSearchResult,
		dbNISTSearchPathApplication,  commandArguments, firstLocatorFilePath, firstLocatorFolderPath, dialog,
		firstLocatorFileContent, secondLocatorFilePath, secondLocatorFolderPath, secondLocatorFileContent, data,
		spectrumContentInNISTFormat, spectrumContentInNISTFormatFilePath, spectrumContentInNISTFormatFolderPath,
		showResultsType, backgroundSearchStatusFilename, backgroundSearchResultsContentFilename, showResultsInMnova,
		showDialog, flagBadOption, props, availableOptions;

	//---------------------Instance attributes to store the settings---------------------

	//It defines the environment variable which contains the ini file
	dbPeakSearchMSInNIST.prototype.environmentVarName = "windir";

	//It sets a list of possible directories which could store the ini file. This var is an alternative solution if the var name does not exist
	dbPeakSearchMSInNIST.prototype.possiblePathsForIniFile = ["C:/Windows", "C:/WINNT"];

	//It defines the name of the ini file which contains the var with the binary path within the expected section
	dbPeakSearchMSInNIST.prototype.iniFileName = "win.ini";

	//It configures the name of the section within the ini file which contains the var of the binary file path
	dbPeakSearchMSInNIST.prototype.winIniExpectedSection = "[NISTMS]";

	//Instance attribute which allows to retrieve the MS Search Program’s folder (location of the executable files). It should work both with 32 and 64 bits versions (ie: Path=, Path32=, Path64=)
	/*jslint regexp: true*/
	dbPeakSearchMSInNIST.prototype.applicationPathRegex = /^Path\d*\s*=\s*.*$/mi;
	/*jslint regexp: false*/

	//It set the filename of the executable binary file
	dbPeakSearchMSInNIST.prototype.executableFilename = "NISTMS$.EXE";

	//It contains the text string describing the name and location of the second locator file. It must be in the MS Search Program’s working directory
	dbPeakSearchMSInNIST.prototype.firstLocatorFilename = "AUTOIMP.MSD";

	//Folder path where the second locator filen will be placed
	dbPeakSearchMSInNIST.prototype.secondLocatorFolderPath = Dir.temp();

	//It contains the text string that describes the name and location of the text file containing the spectrum in the NIST Text file format. Is created each time spectra are saved to a separate file to be imported by the MS Search Program
	dbPeakSearchMSInNIST.prototype.secondLocatorFilename = "AUTOIMP2.MSD";

	//Folder path where the file containing spectrum in NIST text format will be placed
	dbPeakSearchMSInNIST.prototype.spectrumContentInNISTFormatFolderPath = Dir.temp();

	//This file contains the content of the spectrum in the NIST Text file format.
	dbPeakSearchMSInNIST.prototype.spectrumContentInNISTFormatPath = "DATAFILE.TXT";

	//Name of the parameter to fill the field name of the file which contains the spectrum data
	dbPeakSearchMSInNIST.prototype.parametersUsedForNameInSpectrumFile = ["Title", "title", "Description", "description", "Comment", "comment"];

	//This file indicates when the background search, used for bring back results to Mnova has been done
	dbPeakSearchMSInNIST.prototype.backgroundSearchDoneFilename = "SRCREADY.TXT";

	//This file cotains the hit lists needed to bring back the results to Mnova
	dbPeakSearchMSInNIST.prototype.backgroundSearchResultsFilename = "SRCRESLT.TXT";

	//---------------------Instance attributes---------------------
	dbPeakSearchMSInNIST.prototype.generateSpectrumFileContent = function (aMassSpectrum, aParameters) {

		var i, massSpectrum, peaks, peak, fileContent, currentParameter, parameters,
			auxMass, auxIntensity, mass, intensity, previousMass, maxIntensity, decimals,
			nameContent, peaksNumberContent, mzIntensityContent, mzIntensityArray, nextPeak,
			nextMass, nextIntensity;

		//Get mass spectrum and initialize vars
		massSpectrum = aMassSpectrum;
		parameters = aParameters;
		peaks = massSpectrum.peaks;
		nameContent = dbPeakSearchMSInNIST.nameFieldInNISTTextFormat + " ";
		decimals = dbPeakSearchMSInNIST.numberOfDecimalsInPairMassIntensity;
		mzIntensityContent = "";
		mzIntensityArray = [];

		//Generate the content of the spectrum file
		if (massSpectrum !== undefined && massSpectrum.valid !== undefined && massSpectrum.valid) {

			//Get the first line (field name) of the file containing spectrum
			if (parameters !== undefined && (typeof (parameters) === "object")) {
				for (i = 0; i < dbPeakSearchMSInNIST.prototype.parametersUsedForNameInSpectrumFile.length; i++) {
					currentParameter = dbPeakSearchMSInNIST.prototype.parametersUsedForNameInSpectrumFile[i];
					if (currentParameter !== undefined && currentParameter !== "") {
						if (parameters[currentParameter] !== undefined && parameters[currentParameter].toString().trim() !== "" && parameters[currentParameter].toString().trim() !== "undefined") {
							nameContent = dbPeakSearchMSInNIST.nameFieldInNISTTextFormat + " " + parameters[currentParameter];
							break;
						}
					}
				}
			}

			//Get each pair m/z intensity values
			if (peaks !== undefined && peaks.length > 0) {

				//Second line of the file: number of peaks.
				peaksNumberContent = "\n" + dbPeakSearchMSInNIST.namberOfPeaksFieldInNISTTextFormat + " " + peaks.length;

				//Initialize the maximum intensity to the firt value
				if (!isNaN(decimals) && (decimals > -1)) {
					auxIntensity = Number(peaks[0].center.y);
					maxIntensity = Number(auxIntensity.toFixed(decimals));

				} else {
					maxIntensity = Number(peaks[0].center.y);
				}

				//It composes the second line of the file containing spectrum
				for (i = 0; i < peaks.length; i++) {
					peak = peaks[i];
					nextPeak = undefined;
					if (i < (peaks.length - 1)) {
						nextPeak = peaks[i + 1];
					}

					//Get mass and peak values rounding decimals if it is needed
					mass = peak.center.x;
					intensity = peak.center.y;

					//Rounding decimals
					if (!isNaN(decimals) && (decimals > -1)) {

						//Current values
						auxMass = Number(peak.center.x);
						auxIntensity = Number(peak.center.y);
						mass = Number(auxMass.toFixed(decimals));
						intensity = Number(auxIntensity.toFixed(decimals));

						//Next values
						if (nextPeak !== undefined) {
							auxMass = Number(nextPeak.center.x);
							auxIntensity = Number(nextPeak.center.y);
							nextMass = Number(auxMass.toFixed(decimals));
							nextIntensity = Number(auxIntensity.toFixed(decimals));
						}

					//Working with the maximum precission
					} else {

						//Current values
						mass = Number(peak.center.x);
						intensity = Number(peak.center.y);

						//Next values
						if (nextPeak !== undefined) {
							nextMass = Number(nextPeak.center.x);
							nextIntensity = Number(nextPeak.center.y);
						}
					}

					//If the mass is repeated we get the maximum intensity value.
					//This may happen as less decimals we use. The peaks are ordered.
					if (previousMass === mass) {

						//Update the maxIntensity var with the highest value
						if (intensity > maxIntensity) {
							maxIntensity = intensity;
						}

						//Update the last position of the array with the new each pair m/z intensity
						mzIntensityArray[mzIntensityArray.length - 1] = mass + " " + maxIntensity;

					} else {

						//Write each pair m/z intensity in the next position of the array
						mzIntensityArray.push(mass + " " + maxIntensity);

						//Update previous mass value
						previousMass = mass;
					}

					//Reset the maxIntensity value for the next new mass value comparing
					if (nextPeak !== undefined) {

						if (mass !== nextMass) {
							maxIntensity = nextIntensity;
						} else if (nextIntensity > maxIntensity) {
							maxIntensity = nextIntensity;
						}
					}
				}

				//Compose peak number and spectrum content
				peaksNumberContent = "\n" + dbPeakSearchMSInNIST.namberOfPeaksFieldInNISTTextFormat + " " + mzIntensityArray.length;

				//Generate string of pairs mz/intensity using the array
				//The peaks need not be normalized, and the masses need not be ordered.
				for (i = 0; i <  mzIntensityArray.length; i++) {
					mzIntensityContent += "\n" + mzIntensityArray[i];
				}

			} else {
				peaksNumberContent = "\n" + dbPeakSearchMSInNIST.namberOfPeaksFieldInNISTTextFormat + " 0";
			}
		} else {
			peaksNumberContent = "\n" + dbPeakSearchMSInNIST.namberOfPeaksFieldInNISTTextFormat + " 0";
		}

		fileContent = nameContent + peaksNumberContent + mzIntensityContent;

		return fileContent;
	};

	//---------------------Main Program---------------------

	//This search feature only works in computer with Windows operating systems installed
	if (SysInfo.isWin) {

		dbNISTSearchResult = dbPeakSearchMSInNIST.checkApplicationStatus(dbPeakSearchMSInNIST.prototype.environmentVarName, dbPeakSearchMSInNIST.prototype.possiblePathsForIniFile, dbPeakSearchMSInNIST.prototype.iniFileName, dbPeakSearchMSInNIST.prototype.winIniExpectedSection, dbPeakSearchMSInNIST.prototype.applicationPathRegex);
		if (!(dbNISTSearchResult.isInstalled)) {
			MessageBox.critical(qsTr("NIST MS Search program not detected on this computer"));
		} else {

			//Get the base folder of the NIST Search Program
			dbNISTSearchPathApplication = dbNISTSearchResult.path;
			dbNISTSearchPathApplication += "/" + dbPeakSearchMSInNIST.prototype.executableFilename;

			//Check for the executable binary file
			if (!File.exists(dbNISTSearchPathApplication)) {
				MessageBox.critical(qsTr("NIST MS Search program not detected on this computer"));
			} else {

				//Get massSpectrum to perform the search against NIST database
				massItem = Application.mass.activeItem();
				if (massItem === undefined) {
					MessageBox.critical(qsTr("A mass spectrum is needed to perform the search"));
				} else if (!massItem.isValid()) {
					MessageBox.critical(qsTr("The active mass spectrum is not valid"));
				} else {

					//Get mass spectrum from the mass item
					plotsCount = massItem.plotsCount;
					flagSpectrum = -1;
					for (i = 0; i < plotsCount; i++) {
						plot = massItem.plot(i);
						if (plot.type === MassItem.DataTypes.Spectrum) {
							flagSpectrum = i;
							break;
						}
					}

					//Generate the content of the file containing spectrum
					if (flagSpectrum > -1) {
						massSpectrum = massItem.plot(flagSpectrum);
						parameters = massItem.parameters;
						peaks = massSpectrum.peaks;
						spectrumContentInNISTFormat = dbPeakSearchMSInNIST.prototype.generateSpectrumFileContent(massSpectrum, parameters);

						//The process will be taken if we have at least a peak
						if (peaks.length === 0) {
							MessageBox.critical(qsTr("No peaks found in the active mass spectrum"));

						//If the content of the spectra is nice we created the files needed to perform the search in the MS Search program
						} else if (spectrumContentInNISTFormat !== undefined) {
							if (spectrumContentInNISTFormat !== "") {

								if (aOption === undefined) {

									//Show dialog to know how the results be showed
									dialog = Application.loadUiFile('ricares:dbPeakSearchMSInNIST.ui');
									dialog.show();

									//Restore previous selection
									data = settings.value(dbPeakSearchMSInNIST.showResultsOptionKey, -1);
									if (data !== -1 && !isNaN(data)) {
										dialog.widgets.cmbxResults.currentIndex = data;
									}

									//Launch dialog
									showDialog = true;
									while (showDialog) {

										//If user pressed cancel button nothing happens
										if (!dialog.exec()) {
											return;
										}

										showDialog = false;
									}

									//Save settings if user has clicked the OK button
									settings.setValue(dbPeakSearchMSInNIST.showResultsOptionKey, dialog.widgets.cmbxResults.currentIndex);
									showResultsType = dialog.widgets.cmbxResults.currentIndex;

								} else {

									//Options management
									availableOptions = [];
									flagBadOption = true;
									for (props in dbPeakSearchMSInNIST.showResultsType) {
										if (dbPeakSearchMSInNIST.showResultsType.hasOwnProperty(props)) {
											if (props === aOption) {
												flagBadOption = false;
											}
											availableOptions.push(props);
										}
									}

									if (!flagBadOption) {
										showResultsType = dbPeakSearchMSInNIST.showResultsType[aOption];
										settings.setValue(dbPeakSearchMSInNIST.showResultsOptionKey, showResultsType);
									} else {
										MessageBox.critical("Wrong option used. Available options are: {0}".format(availableOptions.toString()));
										return;
									}
								}

								//First locator management
								firstLocatorFolderPath = dbNISTSearchResult.path;
								firstLocatorFilePath = firstLocatorFolderPath + "/" + dbPeakSearchMSInNIST.prototype.firstLocatorFilename;
								firstLocatorFileContent = dbPeakSearchMSInNIST.prototype.secondLocatorFolderPath + "/" + dbPeakSearchMSInNIST.prototype.secondLocatorFilename;
								if (dbPeakSearchMSInNIST.writeToFile(firstLocatorFilePath, firstLocatorFileContent)) {

									//Second locator management
									secondLocatorFolderPath = dbPeakSearchMSInNIST.prototype.secondLocatorFolderPath;
									secondLocatorFilePath = secondLocatorFolderPath + "/" + dbPeakSearchMSInNIST.prototype.secondLocatorFilename;
									secondLocatorFileContent = dbPeakSearchMSInNIST.prototype.spectrumContentInNISTFormatFolderPath + "/" + dbPeakSearchMSInNIST.prototype.spectrumContentInNISTFormatPath + " OVERWRITE";
									if (dbPeakSearchMSInNIST.writeToFile(secondLocatorFilePath, secondLocatorFileContent)) {

										//Third file containing spectrum data in NIST text format
										spectrumContentInNISTFormatFolderPath = dbPeakSearchMSInNIST.prototype.spectrumContentInNISTFormatFolderPath;
										spectrumContentInNISTFormatFilePath = spectrumContentInNISTFormatFolderPath + "/" + dbPeakSearchMSInNIST.prototype.spectrumContentInNISTFormatPath;
										if (dbPeakSearchMSInNIST.writeToFile(spectrumContentInNISTFormatFilePath, spectrumContentInNISTFormat)) {

											//We call the NIST MS Search program by using a QProcess in order to execute the windows exe file
											commandArguments = [];
											commandArguments.push("/INSTRUMENT");

											//To bring back the results to Mnova we have to perform several extra actions
											if (showResultsType === dbPeakSearchMSInNIST.showResultsType.MNOVA) {

												//Add second extra parameter for calling the search process
												commandArguments.push("/PAR=2");
												showResultsInMnova = true;

												//Delete the results file
												backgroundSearchStatusFilename = dbNISTSearchResult.path + "/" + dbPeakSearchMSInNIST.prototype.backgroundSearchDoneFilename;
												backgroundSearchResultsContentFilename = dbNISTSearchResult.path + "/" + dbPeakSearchMSInNIST.prototype.backgroundSearchResultsFilename;
												File.remove(backgroundSearchStatusFilename);
												File.remove(backgroundSearchResultsContentFilename);

												//Create a new process which check when the results are readyState
												dbPeakSearchMSInNIST.prototype.backgroundSearch = {};
												dbPeakSearchMSInNIST.currentTimeOutToShowResutlsProcesInMS = 0;
												dbPeakSearchMSInNIST.prototype.backgroundSearch.backgroundSearchStatusFilename = backgroundSearchStatusFilename;
												dbPeakSearchMSInNIST.prototype.backgroundSearch.backgroundSearchResultsContentFilename = backgroundSearchResultsContentFilename;
												dbPeakSearchMSInNIST.prototype.backgroundSearch.process = new Timer();
												dbPeakSearchMSInNIST.prototype.backgroundSearch.process.singleShot = false;

											} else {
												showResultsInMnova = false;
											}

											//Launch the NIST Search Application
											dbPeakSearchMSInNIST.prototype.dbNISTApplicationProcess = new Process();

											//Create a progress dialog
											dbPeakSearchMSInNIST.prototype.dbNISTApplicationProcess.progressDialog = new ProgressDialog();
											dbPeakSearchMSInNIST.prototype.dbNISTApplicationProcess.progressDialog.labelText = "Searching in NIST MS Search...";
											dbPeakSearchMSInNIST.prototype.dbNISTApplicationProcess.progressDialog.showCancelButton = false;
											dbPeakSearchMSInNIST.prototype.dbNISTApplicationProcess.progressDialog.minimum = 0;
											dbPeakSearchMSInNIST.prototype.dbNISTApplicationProcess.progressDialog.maximum = 0;
											dbPeakSearchMSInNIST.prototype.dbNISTApplicationProcess.progressDialog.visible = true;
											dbPeakSearchMSInNIST.prototype.dbNISTApplicationProcess.progressDialog.update();
											dbPeakSearchMSInNIST.prototype.dbNISTApplicationProcess.isAnMnovaResultType = (showResultsType === dbPeakSearchMSInNIST.showResultsType.MNOVA);

											if (showResultsInMnova) {
												dbPeakSearchMSInNIST.prototype.backgroundSearch.progressDialog = dbPeakSearchMSInNIST.prototype.dbNISTApplicationProcess.progressDialog;
												dbPeakSearchMSInNIST.prototype.backgroundSearch.process.timeout.connect(dbPeakSearchMSInNIST.prototype.backgroundSearch, dbPeakSearchMSInNIST.manageResultsForBackgroundSearch);
											}

											//Perform the search
											dbPeakSearchMSInNIST.prototype.dbNISTApplicationProcess["finished(int)"].connect(dbPeakSearchMSInNIST.prototype.dbNISTApplicationProcess, dbPeakSearchMSInNIST.NISTMSSearchFinished);
											dbPeakSearchMSInNIST.prototype.dbNISTApplicationProcess.start(dbNISTSearchPathApplication, commandArguments);

											//It checks if the program was launched successfully
											if (!dbPeakSearchMSInNIST.prototype.dbNISTApplicationProcess.waitForStarted()) {
												MessageBox.critical(qsTr("Failed to start the NIST MS Search Program\nIf NIST program is opened, close it and try again"));
											} else if (showResultsInMnova) {
												dbPeakSearchMSInNIST.prototype.backgroundSearch.process.start(dbPeakSearchMSInNIST.showResutlsProcessTimerOutInMS);
											}

										} else {
											MessageBox.critical(qsTr("Unable to create the file containing the spectrum in the NIST text format in:\n".format(spectrumContentInNISTFormatFolderPath)));
										}
									} else {
										MessageBox.critical(qsTr("Unable to create the second locator file in:\n{0}".format(secondLocatorFolderPath)));
									}
								} else {
									MessageBox.critical(qsTr("Unable to create the first locator file in:\n{0}".format(firstLocatorFolderPath)));
								}
							} else {
								MessageBox.critical(qsTr("Unable to generate the content of the spectrum in the NIST text format"));
							}
						} else {
							MessageBox.critical(qsTr("Unable to generate the content of the spectrum in the NIST text format"));
						}
					} else {
						MessageBox.critical(qsTr("The active mass item does not contain a mass spectrum"));
					}
				}
			}
		}
	} else if (SysInfo.isLinux) {
		MessageBox.critical(qsTr("It has been detected a GNU/Linux operating system but this feature only works with Microsoft Windows"));
	} else if (SysInfo.isMac) {
		MessageBox.critical(qsTr("It has been detected a Macintosh operating system but this feature only works with Microsoft Windows"));
	} else {
		MessageBox.critical(qsTr("The NIST MS Search tool only works in Microsoft Windows operating systems"));
	}
}

//---------------------Static Attributes---------------------
dbPeakSearchMSInNIST.nameFieldInNISTTextFormat = "Name:";
dbPeakSearchMSInNIST.namberOfPeaksFieldInNISTTextFormat = "Num Peaks:";
dbPeakSearchMSInNIST.numberOfDecimalsInPairMassIntensity = -1;
dbPeakSearchMSInNIST.showResultsOptionKey = "NIST Peak Search/Show Results Option";
dbPeakSearchMSInNIST.showRowsInMoleculeSearch = "NIST Peak Search/Show Rows in Molecule Search";
dbPeakSearchMSInNIST.showResutlsProcessTimerOutInMS = 1000;
dbPeakSearchMSInNIST.timeOutToShowResutlsProcesInMS = 30000;
dbPeakSearchMSInNIST.currentTimeOutToShowResutlsProcesInMS = 0;
dbPeakSearchMSInNIST.autoRetryFeatureEnabled = true;
dbPeakSearchMSInNIST.showResultsType = {
	NIST: 0,
	MNOVA: 1
};

//---------------------Static Methods---------------------

dbPeakSearchMSInNIST.readFile = function (aPath, aNumLines) {
	"use strict";

	var stream, file, string, totalLines, auxString;

	file = new File(aPath);
	file.open(File.ReadOnly);
	stream = new TextStream(file);
	if (aNumLines !== undefined && aNumLines > 0) {
		string = "";
		totalLines = 0;
		while (!stream.atEnd() && totalLines < aNumLines) {
			string += stream.readLine() + "\n";
			totalLines++;
		}
		if (totalLines === 1 && string.length > 2) {
			string = string.slice(0, -1);
			auxString = string.split(/\r?\n|\n|\r/g, aNumLines - 1);
			string = auxString.join("\n");
		}
		stream = string;
	} else {
		stream = stream.readAll();
	}

	file.close();

	return stream;
};

dbPeakSearchMSInNIST.writeToFile = function (aFilePath, aData) {
	"use strict";

	var file, resultFile, result;

	//Write string into a file
	result = false;
	file = new File(aFilePath);
	if (File.exists(aFilePath)) {
		File.remove(aFilePath);
	}
	file.open(File.ReadWrite);
	resultFile = new TextStream(file);
	resultFile.codec = "UTF-8";
	resultFile.write(aData);
	resultFile.flush();
	file.close();
	if (File.exists(aFilePath)) {
		result = true;
	}

	return result;
};

dbPeakSearchMSInNIST.checkApplicationStatus = function (aEnvironmentVarName, aPossiblePathsForIniFile, aIniFileName, aIniSectionExpected, aApplicationPathRegex) {
	"use strict";

	var i, j, result, winDir, winDirPath, envVarName, envVars, flagWinDir, auxDir,
		winIniFilePaths, flagAlternativePaths, winIniFilePath, winIniFileContent,
		winIniExpectedSection, winIniFilePos, winIniSection, applicationPathRegex,
		applicationPathString, resultRegex, applicationPathArray, auxPathApplication;

	//Initialize local vars
	result = {};
	result.isInstalled = false;
	winIniExpectedSection = aIniSectionExpected;
	applicationPathRegex = aApplicationPathRegex;
	winDir = undefined;
	flagAlternativePaths = false;

	//Location of the NIST MS Search Program
	//It is necessary to determine the location of the NIST MS Search Program file and the location of the NIST MS Search working directory.
	//These may be found in the section of the INI file located in the %windir% directory.
	envVars = Process.systemEnvironmentObject();
	flagWinDir = false;
	for (envVarName in envVars) {
		if (envVars.hasOwnProperty(envVarName)) {
			if (envVarName.toString().toLowerCase() === aEnvironmentVarName) {
				winDirPath = envVars[envVarName];
				winDir = new Dir(winDirPath);
				if (winDir.exists) {
					flagWinDir = true;
					break;
				} else {
					winDir = undefined;
					winDirPath = undefined;
				}
			}
		}
	}

	//Now we look for the most common directories if we didn't find the right env var.
	if (aPossiblePathsForIniFile !== undefined) {
		for (i = 0; ((flagWinDir && !flagAlternativePaths) || (i < aPossiblePathsForIniFile.length)); i++) {

			//This prior check allows to know if the windir has been found by using the environment vars
			if (winDir === undefined) {
				if (!flagWinDir && !flagAlternativePaths) {
					flagAlternativePaths = true;
					i = -1;
					continue;
				}

				//Get the directory object from the possible path list
				winDirPath = aPossiblePathsForIniFile[i];
				winDir = new Dir(winDirPath);
			}

			//Looking for the INI file
			if (winDir !== undefined && winDir.exists) {
				winIniFilePaths = winDir.entryListAdv(aIniFileName,  Dir.IgnoreCase, Dir.IgnoreCase);

				//It searchs into each file
				for (j = 0; j < winIniFilePaths.length && !result.isInstalled; j++) {
					winIniFilePath = winIniFilePaths[j];
					winIniFileContent = dbPeakSearchMSInNIST.readFile(winIniFilePath);

					//Looking for the right section of the ini file
					winIniFilePos = winIniFileContent.indexOf(winIniExpectedSection);
					if (winIniFilePos > -1) {
						winIniSection = winIniFileContent.substring(winIniFilePos, winIniFileContent.length);
						resultRegex = winIniSection.match(applicationPathRegex);

						//Get the left part just before the equal symbol which has the value of the PATH
						if (resultRegex !== null && resultRegex.length > 0) {
							applicationPathString = resultRegex[0];
							applicationPathArray = applicationPathString.split("=");
							if (applicationPathArray.length > 1) {

								//Remove both possible blank spaces and quotes
								auxPathApplication = applicationPathArray[1].toString().trim();
								applicationPathString = auxPathApplication.replace(/"/g, "");
								auxPathApplication = applicationPathString;
								applicationPathString = auxPathApplication.replace(/'/g, "");

								//Despite finding the directory, it only will be considered as installed if it exists
								if (applicationPathString !== undefined && applicationPathString !== "") {
									auxDir = new Dir(applicationPathString);
									if (auxDir.exists) {
										result.isInstalled = true;
										result.path = auxDir.absPath;
										return result;
									}
								}
							}
						}
					}
				}
				if (!result.isInstalled) {
					winDir = undefined;
					if (i === 0 && flagWinDir) {
						flagWinDir = false;
					}
				}
			} else {
				winDir = undefined;
			}
		}
	}

	return result;
};

dbPeakSearchMSInNIST.NISTMSSearchFinished = function () {
	"use strict";

	if (this.progressDialog !== undefined) {
		if (this.isAnMnovaResultType !== undefined) {
			if (!this.isAnMnovaResultType) {
				this.progressDialog.visible = false;
			}
		}
		this.progressDialog.update();
		this.progressDialog.raise();

	}

	if (this.exitStatus !== 0) {
		MessageBox.warning(qsTr("A problem has ocurred running the NIST MS Search program"));
	}
};

dbPeakSearchMSInNIST.manageResultsForBackgroundSearch = function () {
	"use strict";

	var auxReadyFileContent, readyFileContent, resultsContent, errorString;

	//Time out management
	if (dbPeakSearchMSInNIST.currentTimeOutToShowResutlsProcesInMS === undefined || isNaN(dbPeakSearchMSInNIST.currentTimeOutToShowResutlsProcesInMS)) {
		dbPeakSearchMSInNIST.currentTimeOutToShowResutlsProcesInMS = 0;
	} else {
		dbPeakSearchMSInNIST.currentTimeOutToShowResutlsProcesInMS += dbPeakSearchMSInNIST.showResutlsProcessTimerOutInMS;
	}

	if (File.exists(this.backgroundSearchStatusFilename)) {
		auxReadyFileContent = dbPeakSearchMSInNIST.readFile(this.backgroundSearchStatusFilename);
		readyFileContent = auxReadyFileContent.trim().replace(/\r\n|\n\r/g, "");
		if (readyFileContent === "1") {

			//Create a viewer to show the results
			resultsContent = dbPeakSearchMSInNIST.readFile(this.backgroundSearchResultsContentFilename);
			try {
				if (Application.resultsViewerForNIST !== undefined && Application.resultsViewerForNIST.dockWidget !== undefined) {
					Application.resultsViewerForNIST.dockWidget.show();
				} else {
					Application.resultsViewerForNIST = new DbPeakSearchMSInNIST_ResultsViewer();
					mainWindow.processEvents();
				}
				Application.resultsViewerForNIST.showResults(resultsContent);
				dbPeakSearchMSInNIST.flagAutoRetry = false;

			} catch (Err) {
				MessageBox.critical(Err);
				Application.resultsViewerForNIST = undefined;
				dbPeakSearchMSInNIST.currentTimeOutToShowResutlsProcesInMS = 0;
			}

			//Finish the backgroun process
			try {
				if (this.process !== undefined) {
					this.process.deleteLater();
				}
			} catch (err) {
				MessageBox.critical(qsTr("Error finishing the background process"));
			} finally {
				if (this.process !== undefined) {
					this.process.deleteLater();
					this.process = undefined;
				}
				if (this.progressDialog !== undefined) {
					this.progressDialog.visible = false;
				}
			}
		}
	}

	if (dbPeakSearchMSInNIST.currentTimeOutToShowResutlsProcesInMS > dbPeakSearchMSInNIST.timeOutToShowResutlsProcesInMS) {
		try {
			errorString = qsTr("Timeout to start the NIST MS Search Program");
		} catch (e) {
			errorString += "\n" + qsTr("Error finishing the background process");
		} finally {
			errorString += "\n" + qsTr("Please try your search again");
			if (this.process !== undefined) {
				this.process.deleteLater();
				this.process = undefined;
			}
			if (this.progressDialog !== undefined) {
				this.progressDialog.visible = false;
			}

			//It performs the search again automatically by using the last user option
			//This option only must be at this option the "show results in MestReNova" option
			if (dbPeakSearchMSInNIST.autoRetryFeatureEnabled) {
				if (dbPeakSearchMSInNIST.flagAutoRetry === undefined || !dbPeakSearchMSInNIST.flagAutoRetry) {
					dbPeakSearchMSInNIST("MNOVA");
					dbPeakSearchMSInNIST.flagAutoRetry = true;
				} else {
					MessageBox.critical(errorString);
					dbPeakSearchMSInNIST.flagAutoRetry = false;
				}
			} else {
				MessageBox.critical(errorString);
				dbPeakSearchMSInNIST.flagAutoRetry = false;
			}
		}
	}
};


if (this.MnUi && this.MnUi.scripts_common) {
	MnUi.scripts_common.scripts_common_PeakSearchMSinNIST = dbPeakSearchMSInNIST;
}